package cn.tedu.bonfire.passport.service.impl;

import cn.hutool.captcha.LineCaptcha;
import cn.tedu.bonfire.common.cache.IUserCacheRepository;
import cn.tedu.bonfire.common.enumerator.ServiceCode;
import cn.tedu.bonfire.common.ex.ServiceException;
import cn.tedu.bonfire.common.pojo.authentication.CurrentPrincipal;
import cn.tedu.bonfire.common.pojo.po.CodeCaptchaCachePO;
import cn.tedu.bonfire.common.pojo.po.PhoneCaptchaCachePO;
import cn.tedu.bonfire.common.pojo.po.UserCachePO;
import cn.tedu.bonfire.common.util.CodeCaptchaUtils;
import cn.tedu.bonfire.common.util.SMS;
import cn.tedu.bonfire.passport.dao.persist.repository.IUserLoginLogRepository;
import cn.tedu.bonfire.passport.dao.persist.repository.IUserRepository;
import cn.tedu.bonfire.passport.pojo.entity.LoginLog;
import cn.tedu.bonfire.passport.pojo.param.AdminUserLoginParam;
import cn.tedu.bonfire.passport.pojo.param.UserLoginInfoParam;
import cn.tedu.bonfire.passport.pojo.param.UserPhoneLoginParam;
import cn.tedu.bonfire.passport.pojo.vo.UserLoginInfoVO;
import cn.tedu.bonfire.passport.pojo.vo.UserLoginResultVO;
import cn.tedu.bonfire.passport.service.IUserService;
import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;

@Slf4j
@Service
public class UserServiceImpl implements IUserService {

    @Value("${bonfire.jwt.secret-key}")
    private String secretKey;

    @Autowired
    private IUserRepository userRepository;

    @Autowired
    private IUserLoginLogRepository userLoginLogRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private IUserCacheRepository userCacheRepository;


    @Override
    public UserLoginResultVO login(UserLoginInfoParam userLoginInfoParam, String remoteAddr, String userAgent) {
        log.debug("开始处理【用户登录】的业务，参数：{}", userLoginInfoParam);

        // 从参数中获取用户名
        String username = userLoginInfoParam.getUsername();
        // 调用Repository根据用户名查询用户信息
        UserLoginInfoVO loginInfo = userRepository.getLoginInfoByUsername(username);
        // 判断查询结果是否为null
        // 是：抛出异常（unauthorized:40100）：用户名或密码错误
        if (loginInfo == null) {
            String message = "登录失败，用户名或密码错误！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_UNAUTHORIZED, message);
        }
        // 判断查询结果中的enable是否不为1
        // 是：抛出异常（unauthorized_diabled:40101）：账号已经被禁用
        if (loginInfo.getEnable() != 1 ) {
            String message = "登录失败，此账号已经被禁用！";
            throw new ServiceException(ServiceCode.ERROR_UNAUTHORIZED_DISABLED, message);
        }

        // 从参数中获取密码
        // 判断查询结果中的密码与参数密码是否不匹配
        // 是：抛出异常（unauthorized:40100）：用户名或密码错误
        String password = userLoginInfoParam.getPassword();
        if (!passwordEncoder.matches(password, loginInfo.getPassword())) {
            String message = "登录失败，用户名或密码错误！";
            throw new ServiceException(ServiceCode.ERROR_UNAUTHORIZED, message);
        }
        /*判断缓存中是否存有数据 */
        String base64String = userLoginInfoParam.getBase64String();
        if (base64String == null){
            String message = "验证码已过期!";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_CAPTCHA_EXPIRED,message);
        }
        String base64 = base64String.split(",")[1];
        String code = userLoginInfoParam.getCode();
        CodeCaptchaCachePO codeCaptcha = userCacheRepository.getCodeCaptcha(base64);
        if (codeCaptcha.getCode() == null) {
            String message = "验证码已过期!";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_CAPTCHA_EXPIRED,message);
        }
        /*校验验证码*/
        if (!codeCaptcha.getCode().equalsIgnoreCase(code)){
            String message = "验证码错误!";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_CAPTCHA,message);
        }

        Long userId = loginInfo.getId();
        // 向登录日志表中插入数据
        LocalDateTime now = LocalDateTime.now();
        LoginLog loginLog = new LoginLog();
        loginLog.setUserId(userId);
        loginLog.setUsername(username);
        loginLog.setUser_agent(userAgent);
        loginLog.setIp(remoteAddr);
        loginLog.setGmtLogin(now);

        int rows = userLoginLogRepository.insert(loginLog);
        if (rows != 1) {
            String message = "登录失败，服务器忙，请稍后再尝试！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_INSERT, message);
        }


        // 准备返回的结果数据
        UserLoginResultVO loginResult = new UserLoginResultVO();
        BeanUtils.copyProperties(loginInfo, loginResult);

        // 生成当前用户对应的JWT，并保存到UserLoginResultVO中
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", loginResult.getId());
        claims.put("username", loginResult.getUsername());
        claims.put("remoteAddr", remoteAddr);
        claims.put("userAgent", userAgent);
        String jwt = Jwts.builder()
                //Header 规则
                .setHeaderParam("alg", "HS256")
                .setHeaderParam("typ", "JWT")
                //Payload 内容
                .setClaims(claims)
                //Verify Signature 校验方式(主要验证是否为本程序发出) 与规则一致
                .signWith(SignatureAlgorithm.HS256, secretKey)
                // 结束
                .compact();
        loginResult.setToken(jwt);

        // 将用户的相关数据写入到Redis中
        List<String> loginInfoAuthorities = loginInfo.getAuthorities();
        String authoritiesJsonString = JSON.toJSONString(loginInfoAuthorities);
        UserCachePO userCachePO = new UserCachePO();
        userCachePO.setAuthoritiesJsonString(authoritiesJsonString);
        userCachePO.setEnable(1);
        userCacheRepository.saveUserState(loginResult.getId(), userCachePO);

        log.debug("即将返回用户的登录结果：{}", loginResult);

        return loginResult;
    }

    @Override
    public UserLoginResultVO adminLogin(AdminUserLoginParam adminUserLoginParam, String remoteAddr, String userAgent) {
        log.debug("开始处理【用户登录】的业务，参数：{}", adminUserLoginParam);

        // 从参数中获取用户名
        String username = adminUserLoginParam.getUsername();
        // 调用Repository根据用户名查询用户信息
        UserLoginInfoVO loginInfo = userRepository.getLoginInfoByUsername(username);
        // 判断查询结果是否为null
        // 是：抛出异常（unauthorized:40100）：用户名或密码错误
        if (loginInfo == null) {
            String message = "登录失败，用户名或密码错误！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_UNAUTHORIZED, message);
        }
        // 判断查询结果中的enable是否不为1
        // 是：抛出异常（unauthorized_diabled:40101）：账号已经被禁用
        if (loginInfo.getEnable() != 1 ) {
            String message = "登录失败，此账号已经被禁用！";
            throw new ServiceException(ServiceCode.ERROR_UNAUTHORIZED_DISABLED, message);
        }

        // 从参数中获取密码
        // 判断查询结果中的密码与参数密码是否不匹配
        // 是：抛出异常（unauthorized:40100）：用户名或密码错误
        String password = adminUserLoginParam.getPassword();
        if (!passwordEncoder.matches(password, loginInfo.getPassword())) {
            String message = "登录失败，用户名或密码错误！";
            throw new ServiceException(ServiceCode.ERROR_UNAUTHORIZED, message);
        }

        Long userId = loginInfo.getId();
        // 向登录日志表中插入数据
        LocalDateTime now = LocalDateTime.now();
        LoginLog loginLog = new LoginLog();
        loginLog.setUserId(userId);
        loginLog.setUsername(username);
        loginLog.setUser_agent(userAgent);
        loginLog.setIp(remoteAddr);
        loginLog.setGmtLogin(now);

        int rows = userLoginLogRepository.insert(loginLog);
        if (rows != 1) {
            String message = "登录失败，服务器忙，请稍后再尝试！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_INSERT, message);
        }


        // 准备返回的结果数据
        UserLoginResultVO loginResult = new UserLoginResultVO();
        BeanUtils.copyProperties(loginInfo, loginResult);

        // 生成当前用户对应的JWT，并保存到UserLoginResultVO中
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", loginResult.getId());
        claims.put("username", loginResult.getUsername());
        claims.put("remoteAddr", remoteAddr);
        claims.put("userAgent", userAgent);
        String jwt = Jwts.builder()
                //Header 规则
                .setHeaderParam("alg", "HS256")
                .setHeaderParam("typ", "JWT")
                //Payload 内容
                .setClaims(claims)
                //Verify Signature 校验方式(主要验证是否为本程序发出) 与规则一致
                .signWith(SignatureAlgorithm.HS256, secretKey)
                // 结束
                .compact();
        loginResult.setToken(jwt);

        // 将用户的相关数据写入到Redis中
        List<String> loginInfoAuthorities = loginInfo.getAuthorities();
        String authoritiesJsonString = JSON.toJSONString(loginInfoAuthorities);
        UserCachePO userCachePO = new UserCachePO();
        userCachePO.setAuthoritiesJsonString(authoritiesJsonString);
        userCachePO.setEnable(1);
        userCacheRepository.saveUserState(loginResult.getId(), userCachePO);

        log.debug("即将返回用户的登录结果：{}", loginResult);

        return loginResult;
    }


    /* 手机验证码登录 */
    @Override
    public UserLoginResultVO loginByPhone(UserPhoneLoginParam userPhoneLoginParam, String remoteAddr, String userAgent) {
        // 获取电话号码
        String phone = userPhoneLoginParam.getPhone();
        // 根据电话号码匹配用户信息
        UserLoginInfoVO loginInfo = userRepository.getLoginInfoByPhone(phone);

        // 判断查询结果中的enable是否不为1
        // 是：抛出异常（unauthorized_diabled:40101）：账号已经被禁用
        if (loginInfo.getEnable() != 1 ) {
            String message = "登录失败，此账号已经被禁用！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_UNAUTHORIZED_DISABLED, message);
        }
        String captcha = userPhoneLoginParam.getCaptcha();
        PhoneCaptchaCachePO phoneCaptcha = userCacheRepository.getPhoneCaptcha(phone);
        if (phoneCaptcha == null){
            String message = "验证码已过期!";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_CAPTCHA_EXPIRED,message);
        }

        if (!phoneCaptcha.getCaptcha().equals(captcha)) {
            String message = "验证码错误!";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_CAPTCHA, message);
        }

        Long userId = loginInfo.getId();
        String username = loginInfo.getUsername();
        // 向登录日志表中插入数据
        LocalDateTime now = LocalDateTime.now();
        LoginLog loginLog = new LoginLog();
        loginLog.setUserId(userId);
        loginLog.setUsername(username);
        loginLog.setUser_agent(userAgent);
        loginLog.setIp(remoteAddr);
        loginLog.setGmtLogin(now);

        int rows = userLoginLogRepository.insert(loginLog);
        if (rows != 1) {
            String message = "登录失败，服务器忙，请稍后再尝试！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERROR_INSERT, message);
        }

        // 准备返回的结果数据
        UserLoginResultVO loginResult = new UserLoginResultVO();
        BeanUtils.copyProperties(loginInfo, loginResult);

        // 生成当前用户对应的JWT，并保存到UserLoginResultVO中
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", loginResult.getId());
        claims.put("username", loginResult.getUsername());
        claims.put("remoteAddr", remoteAddr);
        claims.put("userAgent", userAgent);
        String jwt = Jwts.builder()
                //Header 规则
                .setHeaderParam("alg", "HS256")
                .setHeaderParam("typ", "JWT")
                //Payload 内容
                .setClaims(claims)
                //Verify Signature 校验方式(主要验证是否为本程序发出) 与规则一致
                .signWith(SignatureAlgorithm.HS256, secretKey)
                // 结束
                .compact();
        loginResult.setToken(jwt);

        // 将用户的相关数据写入到Redis中
        List<String> loginInfoAuthorities = loginInfo.getAuthorities();
        String authoritiesJsonString = JSON.toJSONString(loginInfoAuthorities);
        UserCachePO userCachePO = new UserCachePO();
        userCachePO.setAuthoritiesJsonString(authoritiesJsonString);
        userCachePO.setEnable(1);
        userCacheRepository.saveUserState(loginResult.getId(), userCachePO);

        log.debug("即将返回用户的登录结果：{}", loginResult);
        return loginResult;
    }

    /*获取手机验证码*/
    @Override
    public void getCaptchaByPhone(String phone) {
        userCacheRepository.deletePhoneCaptcha(phone);
        PhoneCaptchaCachePO phoneCaptchaCachePO = SMS.send(phone);
        userCacheRepository.savePhoneCaptcha(phone, phoneCaptchaCachePO);
    }

    /*获取图形验证码*/
    @Override
    public String getCodeCaptcha() {
        String base64String = "";
        LineCaptcha lineCaptcha = CodeCaptchaUtils.generateCodeCaptcha();
        String code = lineCaptcha.getCode();
        String base64 = lineCaptcha.getImageBase64();
        userCacheRepository.deleteCodeCaptcha(base64);
        base64String = "data:image/png;base64," + base64;
        CodeCaptchaCachePO codeCaptchaCachePO = new CodeCaptchaCachePO();
        codeCaptchaCachePO.setCode(code);
        userCacheRepository.saveCodeCaptcha(base64,codeCaptchaCachePO);
        return base64String;
    }

    @Override
    public void logout(CurrentPrincipal currentPrincipal) {
        Long userId = currentPrincipal.getId();
        userCacheRepository.deleteUserState(userId);
    }

    @Override
    public String getAuthorities(CurrentPrincipal currentPrincipal) {
        Long userId = currentPrincipal.getId();
        UserCachePO userState = userCacheRepository.getUserState(userId);
        String authoritiesJsonString = userState.getAuthoritiesJsonString();
        return authoritiesJsonString;
    }
}
